﻿using System;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using LucyGrid;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Collections.Generic;
using System.Net;
using System.Collections.Concurrent;
using System.Security.Cryptography;

namespace LucyGridTest
{
	[TestClass]
	public class UnitTest1
	{
		private TestContext testContextInstance;

		/// <summary>
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///</summary>
		public TestContext TestContext
		{
			get
			{
				return testContextInstance;
			}
			set
			{
				testContextInstance = value;
			}
		}

		GridComm gcomm;

		[TestInitialize]
		public void Initialize()
		{
			GridUtils.EnableLog = (int)GridUtils.LogType.DEBUG;

			gcomm = new GridComm().StartListener((m) =>
			{
				System.Diagnostics.Debug.WriteLine(m);
			});

		}


		[TestMethod]
		public void Grid_TestGrid()
		{
			GridUtils.EnableLog = (int)GridUtils.LogType.ALL;
			GridUtils.LogCustomOutput = (tags, msg) =>
			{
				if (tags.Contains("MASTER"))
					System.Diagnostics.Debug.WriteLine(msg);
			};

			var l = new GridComm().StartListener();

			int test = 5, res = 0;
			// Run a worker
			var t = StartGridNode(1);
			try
			{
				res = GridMe(test, new GridSettings()
				{
					EnableLocalProcessing = false
				});
			}
			finally
			{
				// Close nodes
				new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, string.Format("Error on the resulting number. Expected {0}. Received {1}",
				test, res));
			Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}

		[TestMethod]
		public void Basic_Grid_Test()
		{
			GridUtils.EnableLog = -1;
			/*GridUtils.CustomOutput = (tags, msg) =>
			{
				if (tags.Contains("MASTER"))
					System.Diagnostics.Debug.WriteLine(msg);
			};*/
			int test = 30, res = 0;
			try
			{
				res = GridMe(test, new GridSettings()
				{
					EnableLocalProcessing = true
				});
			}
			finally
			{
			}
			Assert.IsTrue(res == test, string.Format("Error on the resulting number. Expected {0}. Received {1}",
				test, res));
		}

		[TestMethod]
		public void Grid_TestGridNoLocal()
		{
			var l = new GridComm().StartListener();

			int test = 300, res = 0;
			// Run a worker
			var t = StartGridNode(15);
			try
			{
				res = GridMe(test, new GridSettings() { EnableLocalProcessing = false });
			}
			finally
			{
				// Close nodes
				new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, "Error on the resulting number");
			Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}

		[TestMethod]
		public void Grid_TestGridFaultTolerance()
		{
			var l = new GridComm().StartListener();
			GridUtils.EnableLog = (int)GridUtils.LogType.DEBUG;
			int test = 800, res = 0;
			// Run a worker
			var t = StartGridNode(3);
			try
			{
				Task.Run(() =>
				{
					Thread.Sleep(30000);
					for (var c = 0; c != 5; ++c)
					{
						try
						{
							// Kill one task
							var proc = Process.GetProcesses()
								.First(p => p.ProcessName.IndexOf("LucyGridNode", System.StringComparison.Ordinal) != -1);
							proc.Kill();
						}
						catch (Exception ex)
						{
							System.Diagnostics.Debug.WriteLine(ex.ToString());
						}
						Thread.Sleep(3000);
					}
				});
				res = GridMe(test, new GridSettings() { EnableLocalProcessing = true }, 4000);
			}
			finally
			{
				// Close nodes
				new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, "Error on the resulting number");
			Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}

		[TestMethod]
		public void Grid_TestGridNoLocalPerformance()
		{
			GridUtils.EnableLog = (int)GridUtils.LogType.TOFILE;
			var l = new GridComm().StartListener();

			int test = 300, res = 0;
			// Run a worker
			var t = StartGridNode(30);
			try
			{
				res = GridMeSerializer(test, new GridSettings() { EnableLocalProcessing = true });
			}
			finally
			{
				// Close nodes
				new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, "Error on the resulting number");
			Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}


		[TestMethod]
		public void Grid_TestChunksGrid()
		{
			var l = new GridComm().StartListener();

			int test = 300, res = 0;
			// Run a worker
			//var t = StartGridNode(3);
			try
			{
				res = GridMeChunked(test, 10);
			}
			finally
			{
				// Close nodes
				//new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, "Error on the resulting number");
			//Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}


		[TestMethod]
		public void Grid_TestGridWithExternalNodes()
		{
			var l = new GridComm().StartListener();
			GridUtils.EnableLog = -1;
			int test = 40;
			// Run a worker
			var res = GridMe(test, new GridSettings()
			{
				EnableLocalProcessing = false
			});
			// Close nodes
			// new GridComm().EmitStopAll();
			Assert.IsTrue(res == test, "Error on the resulting number");
		}

		[TestMethod]
		public void Grid_TestRemoteNode_remote_must_be_running()
		{
			// var l = new GridComm().StartListener();

			int test = 50;
			// Run a worker
			//var t = StartGridNode(1);
			var res = GridMe(test);
			// Close nodes
			//new GridComm().EmitStopAll();
			Assert.IsTrue(res == test, "Error on the resulting number");
			// Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}

		[TestMethod]
		public void RunMultiGridNodes()
		{
			StartGridNode(3);
		}

		[TestMethod]
		public void Grid_TestMultiGrid()
		{
			// Run a worker
			var t = StartGridNode(3);
			Enumerable
				.Range(0, 4)
				.AsParallel()
				.Select(i => GridMe(20))
				.ToArray();
			// Close nodes
			new GridComm().EmitStopAll();
			Assert.IsTrue(Task.WaitAll(t, 1000));
		}

		[TestMethod]
		public void Grid_TestYieldMode()
		{
			GridUtils.EnableLog = -1;
			/*GridUtils.CustomOutput = (tags, msg) =>
			{
				if (tags.Contains("MASTER"))
					System.Diagnostics.Debug.WriteLine(msg);
			};*/
			int test = 1000, res = 0;
			var t = StartGridNode(3);
			try
			{
				res = Enumerable.Range(0, test)
					.Select(i =>
					{
						/*if ((i % 100) == 0)
							Thread.Sleep(1000);
						*/
						return i;
					})
					.AsParallelGrid(i => i, new GridSettings()
				{
					EnableLocalProcessing = false
				})
				.Select(i =>
				{
					GridUtils.NotifyLog("TEST,TRACKING", "Tracking, {0}", i);
					return i;
				})
				.ToArray()
				.Length;
			}
			finally
			{
			}
			new GridComm().EmitStopAll();
			// Wait until write log
			Thread.Sleep(20000);
			Assert.IsTrue(Task.WaitAll(t, 1000));
			Assert.IsTrue(res == test, string.Format("Error on the resulting number. Expected {0}. Received {1}",
				test, res));

		}


		Task[] StartGridNode(int nodes)
		{
			var res = Enumerable.Range(0, nodes)
				.Select(i =>
						Task.Run(() =>
						{
							var file = Directory.GetFiles(Path.GetFullPath(TestContext
									.TestRunDirectory + "../../../LucyGridNode/bin/debug")).First(f => f.EndsWith(".exe"));
							var p = Process.Start(file);
							while (!p.WaitForExit(500))
								p.Refresh();
							System.Diagnostics.Debug.WriteLine("Process finished");
						})).ToArray();
			Thread.Sleep(nodes * 1000);
			return res;
		}



		private int GridMe(int test, GridSettings settings = null, int sleeptime = 1000)
		{
			string testValue = "aaarghh";
			var sw = System.Diagnostics.Stopwatch.StartNew();
			int count = 0;
			double firstTime = 0, firstReceived = 0;
			var startTime = DateTime.Now;
			Dictionary<string, int> metrics = new Dictionary<string, int>();
			foreach (var i in Enumerable.Range(0, test)
				.Select(s => new
				{
					StartTime = DateTime.Now,
					Sleeptime = sleeptime,
					Length = 3,
					IsNull = false,
					Complex = new
					{
						CompexLevel = 1,
						Complex = new
						{
							CompexLevel = 2,
							ExternalRef = new SerializableClass()
							{
								Data = testValue,
								IntData = 4,
								SomeArrayData = new string[] {"a", "b"}
							}
						}
					}
				})
				.AsParallelGrid(d =>
				{
					/*var r = new Random();
				var http = HttpWebRequest.Create("http://www.google.com");
				var ev = new ManualResetEvent(false);
				http.BeginGetResponse((ar) =>
				{
					http.EndGetResponse(ar);
					ev.Set();
				}, http);
				ev.WaitOne(5000);*/
					var receivedTime = DateTime.Now;
					Thread.Sleep(d.Sleeptime);
					return new
					{
						StartTime = d.StartTime,
						ReceivedTime = receivedTime,
						EndTime = DateTime.Now,
						Lenght2 = d.Length*2,
						Complex = new
						{
							CompexLevel = 1,
							Complex = new
							{
								CompexLevel = 2,
								d.Complex.Complex.ExternalRef,
								ExternalRef2 = new SerializableClass()
								{
									Data = "From GRID",
									IntData = 100000,
									SomeArrayData = new string[] {"G", "R", "I", "D"}
								}
							}
						}
					};
				}, settings))
			{
				if (count++ == 0)
				{
					firstTime = (DateTime.Now - i.StartTime).TotalMilliseconds;
					firstReceived = (i.ReceivedTime - startTime).TotalMilliseconds;
				}
				if (i.Complex.Complex.ExternalRef.Data != testValue)
					break;
				var key = DateTime.Now.ToString("hh:mm:ss");
				if (metrics.ContainsKey(key))
					metrics[key] = metrics[key] + 1;
				else
					metrics.Add(key, 1);
			}
			Debug.WriteLine("GridMe performs in <{0}> ms, <{1}> s, <{2}> min.\r\n"
				+" First chunk received <{3}> processed in <{4}> ms.\r\n"
				+ "GridMe metrics. Chunks/sec: Min <{5}>, Max <{6}>, Avg: <{7}>", 
				sw.ElapsedMilliseconds, 
				sw.ElapsedMilliseconds / 1000, 
				sw.ElapsedMilliseconds / 60000, 
				firstTime,
				firstReceived,
				metrics.Min(m => m.Value),
				metrics.Max(m => m.Value),
				metrics.Average(m => m.Value));
			return count;
		}

		private int GridMeSerializer(int test, GridSettings settings = null)
		{
			// string testValue = "aaarghh";
			var sw = System.Diagnostics.Stopwatch.StartNew();
			var res = Enumerable.Range(0, test)
			.Select(s => new SerializableClass()
			{

			})
			.AsParallelGrid(d =>
			{
				/*var r = new Random();
				var http = HttpWebRequest.Create("http://www.google.com");
				var ev = new ManualResetEvent(false);
				http.BeginGetResponse((ar) =>
				{
					http.EndGetResponse(ar);
					ev.Set();
				}, http);
				ev.WaitOne(5000);*/
				Thread.Sleep(1000);
				return new SerializationTestClass();
			}, settings)
			.Count();
			System.Diagnostics.Debug.WriteLine("GridMe performs in <" + sw.ElapsedMilliseconds + "> ms");
			return res;
		}


		private int GridMeChunked(int test, int chunks)
		{
			string testValue = "aaarghh";
			return Enumerable.Range(0, test)
			.Select((s, i) => new
			{
				Index = i,
				Length = 3,
				IsNull = false,
				Complex = new
				{
					CompexLevel = 1,
					Complex = new
					{
						CompexLevel = 2,
						ExternalRef = new SerializableClass()
						{
							Data = testValue,
							IntData = 4,
							SomeArrayData = new string[] { "a", "b" }
						}
					}
				}
			})
			.GroupBy(s => s.Index / chunks)
			.Select(g => new { Key = g.Key, Items = g.ToArray() })
			.AsParallelGrid(g =>
			{
				return g.Items.Select(d =>
				{
					var r = new Random();
					var http = HttpWebRequest.Create("http://www.google.com");
					var ev = new ManualResetEvent(false);
					http.BeginGetResponse((ar) =>
					{
						http.EndGetResponse(ar);
						ev.Set();
					}, http);
					ev.WaitOne(5000);
					return new
					{
						Lenght2 = d.Length * 2,
						Complex = new
						{
							CompexLevel = 1,
							Complex = new
							{
								CompexLevel = 2,
								d.Complex.Complex.ExternalRef,
								ExternalRef2 = new SerializableClass()
								{
									Data = "From GRID",
									IntData = 100000,
									SomeArrayData = new string[] { "G", "R", "I", "D" }
								}
							}
						}
					};
				}).ToArray();
			})
			.Where(r => r.First().Complex.Complex.ExternalRef.Data == testValue)
			.Sum(r => r.Count());
		}

		//[TestMethod]
		public void TestNestedGrid()
		{
			Enumerable
				.Range(0, 100)
				.Select(i => new { Value = i })
				.AsParallelGrid(i =>
				{
					var val = Enumerable
							.Range(0, 100)
							.Select(c => new { Value = c })
							.AsParallelGrid(ii =>
							{
								return new { Total = ii.Value + 1 };
							})
							.Sum(cc => cc.Total);
					return new
					{
						Total = val
					};
				})
				.ToArray();
		}

		[Serializable]
		public class SerializableClass
		{
			public string Data;
			public int IntData;
			public string[] SomeArrayData { get; set; }
		}

		[Serializable]
		public class Complex
		{
			public int CompexLevel = 1;
			public SerializableClass ExternalRef;
			public Complex ComplexObject;
		}

		[Serializable]
		public class SerializationTestClass
		{
			public int Length = 3;
			public bool IsNull = false;

			public Complex Complex;
			public SerializableClass[] ArrayOfObject = new SerializableClass[] { 
					new SerializableClass(){
					 IntData = 3
					}
				};
			public SerializationTestClass()
			{
				var c = new SerializableClass()
				{
					Data = "SerializableClassData",
					IntData = 9,
					SomeArrayData = new string[] { "1", "1" }
				};
				Complex = new Complex()
				{
					CompexLevel = 1,
					ComplexObject = new Complex()
					{
						CompexLevel = 2,
						ComplexObject = null,
						ExternalRef = c
					}
				};
			}
		}
		[TestMethod]
		public void Utils_TestChunkSerialization()
		{
			var chunk = new Chunk()
			{
				Head = new Chunk.Header()
				{
					Id = 100
				},
				Data = new
				{
					Test = 1,
					Data = "Some data",
					Object = new
					{
						boolValue = false
					}
				}
			};
			var ms = new MemoryStream();
			chunk.Serialize(ms);
			ms.Seek(0, SeekOrigin.Begin);
			var chunkDes = new Chunk(ms, chunk.Data.GetType());
			Assert.AreEqual(chunk.Data.ToString(), chunkDes.Data.ToString());
		}

		[TestMethod]
		public void Utils_TestSerialization()
		{
			var testObject = new SerializationTestClass()
			{
				Length = 3,
				IsNull = false,
				Complex = new Complex()
				{
					CompexLevel = 1,
					ComplexObject = new Complex()
					{
						CompexLevel = 2,
						ExternalRef = new SerializableClass()
						{
							Data = "aaarghh",
							IntData = 4,
							SomeArrayData = new string[] { "a", "b" }
						}
					}
				},
				//ListOfObjects = new List<NoMarketAsSerializableClass>(),
				ArrayOfObject = new SerializableClass[] { 
					new SerializableClass(){
					 IntData = 3
					}
				}
			};
			var res = SerializeCustom(testObject);
			System.Diagnostics.Debug.WriteLine("Custom serialization takes: " + res[0] + " for " + res[1] + " bytes");
			res = SerializeBinary(testObject);
			System.Diagnostics.Debug.WriteLine("Binary serialization takes: " + res[0] + " for " + res[1] + " bytes");

			string testValue = "testValue";
			var d = new
			{
				Length = 3,
				IsNull = false,
				Complex = new
				{
					CompexLevel = 1,
					Complex = new
					{
						CompexLevel = 2,
						ExternalRef = new SerializableClass()
						{
							Data = testValue,
							IntData = 4,
							SomeArrayData = new string[] { "a", "b" }
						}
					}
				}
			};
			var a = new
			{
				Lenght2 = d.Length * 2,
				Complex = new
				{
					CompexLevel = 1,
					Complex = new
					{
						CompexLevel = 2,
						d.Complex.Complex.ExternalRef,
						ExternalRef2 = new SerializableClass()
						{
							Data = "From GRID",
							IntData = 100000,
							SomeArrayData = new string[] { "G", "R", "I", "D" }
						}
					}
				}
			};

			res = SerializeCustom(a);


			var c = Enumerable.Range(0, 100)
				.Select(i => new
				{
					Index = i,
					Lenght2 = d.Length * 2,
					Complex = new
					{
						CompexLevel = 1,
						Complex = new
						{
							CompexLevel = 2,
							d.Complex.Complex.ExternalRef,
							ExternalRef2 = new SerializableClass()
							{
								Data = "From GRID",
								IntData = 100000,
								SomeArrayData = new string[] { "G", "R", "I", "D" }
							}
						}
					}
				})
				.GroupBy(i => i.Index / 10)
				.Select(f => new { Items = f.ToArray() })
				.First().Items;
			res = SerializeCustom(new string[] { "a" });
			res = SerializeCustom(c);
		}

		public object[] SerializeCustom(object testObject)
		{
			var sp = System.Diagnostics.Stopwatch.StartNew();
			sp.Start();
			using (var ms = new MemoryStream())
			{
				GridUtils.SerializeCustom(ms, testObject);
				ms.Seek(0, SeekOrigin.Begin);
				var size = ms.Length;
				var o = GridUtils.DeserializeCustom(ms, testObject.GetType());
				sp.Stop();
				var time = sp.ElapsedMilliseconds;

				var jsonconv = new Newtonsoft.Json.JsonSerializer();
				var writer = new StringWriter();
				jsonconv.Serialize(writer, testObject);
				var json1 = writer.ToString();
				writer = new StringWriter();
				jsonconv.Serialize(writer, o);
				var json2 = writer.ToString();
				Assert.IsTrue(json1.Equals(json2));
				return new object[] { time, size, o };
			}
		}

		public object[] SerializeBinary(object testObject)
		{
			var sp = System.Diagnostics.Stopwatch.StartNew();
			sp.Start();
			using (var ms = new MemoryStream())
			{
				GridUtils.SerializeBinary(ms, testObject);
				ms.Seek(0, SeekOrigin.Begin);
				var size = ms.Length;
				var o = GridUtils.DeserializeBinary(ms, testObject.GetType());
				sp.Stop();
				var time = sp.ElapsedMilliseconds;

				var jsonconv = new Newtonsoft.Json.JsonSerializer();
				var writer = new StringWriter();
				jsonconv.Serialize(writer, testObject);
				var json1 = writer.ToString();
				writer = new StringWriter();
				jsonconv.Serialize(writer, o);
				var json2 = writer.ToString();
				Assert.IsTrue(json1.Equals(json2));
				return new object[] { time, size, o };
			}
		}


		[TestMethod]
		public void Comm_TestGridUtilsCommListen()
		{
			var t = Task.Run(() => RunCommListener());
			Thread.Sleep(1000);
			RunCommWriter();
			Assert.IsTrue(t.Wait(1000));
		}

		public void RunCommWriter()
		{
			var l = new GridComm();
			l.SendMessage("TEST", "end");
		}

		void RunCommListener()
		{
			var l = new GridComm();
			var end = false;
			l.OnMessageReceived += (message) =>
			{
				end = "end".Equals(message);
				System.Diagnostics.Debug.WriteLine(message);
			};
			l.StartListener();
			while (!end)
			{
				Thread.Sleep(100);
			}
		}

		[TestMethod]
		public void Comm_TestGridUtilsCommEvent()
		{
			var l = new GridComm();
			var resevent = new ManualResetEvent(false);
			l.OnUnknownEventReceived += (ev, data) =>
			{
				Assert.AreEqual("testev", ev);
				Assert.AreEqual("test", data["test"]);
				resevent.Set();
			};
			l.StartListener();
			l.Emit("TEST", "testev", new { test = "test" });
			resevent.WaitOne();
		}

		[TestMethod]
		public void LucyBinManager_TestDynLib()
		{
			var assmfile = string.Format(@"{0}\..\..\DynDummLib\bin\debug\DynDummLib.dll",
					Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));

			var l = new GridComm().StartListener();

			int test = 1, res = 0;
			// Run a worker
			var t = StartGridNode(1);
			try
			{
				var ass = Assembly.LoadFile(assmfile);
				var vals = (new String[] { "" })
					.Select(i => new
					{
						strng = i,
						type = ass.GetTypes().First(assm => assm.Name.Contains("DynDummyClass"))
					})
					.AsParallelGrid(s => new
					{
						Length = s.strng.Length,
						t = Activator.CreateInstance(s.type)
					})
					.ToArray();
				res = vals.Length;
			}
			finally
			{
				// Close nodes
				new GridComm().EmitStopAll();
			}
			Assert.IsTrue(res == test, "Error on the resulting number");
			Assert.IsTrue(Task.WaitAll(t, 5000), "Error waiting tasks to finish");
		}
	}
}
